package fasilkom.antz;


import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Comparator;

import com.golden.gamedev.GameObject;
import com.golden.gamedev.object.AnimatedSprite;
import com.golden.gamedev.object.PlayField;
import com.golden.gamedev.object.Sprite;
import com.golden.gamedev.object.SpriteGroup;
import com.golden.gamedev.object.background.ImageBackground;

import fasilkom.antz.algo.AiManager;
import fasilkom.antz.algo.PathFindingNode;
import fasilkom.antz.background.TiledBackground;
import fasilkom.antz.sprite.CharacterProfile;
import fasilkom.antz.sprite.ItemSprite;
import fasilkom.antz.sprite.PlayingProfile;
import fasilkom.antz.util.Util;


public class Stage extends GameObject {

	// CONSTANT //

	public static final int SCROLL_AREA_WIDTH = 40;
	public static final double SCROLL_SPEED = 0.2;
	private static final int PANEL_WIDTH = 153;

	// PROPERTY //

	public PlayField playField;
	public PlayField hud;

	//public int ant;
	public Sprite mission;
	public Sprite closeButton;
	
	public int enemyHealthPotion;

	//public int totalPotion;
	//public int totalEnergyPotion;
	public int totalAttackUp;
	public int totalDefenseUp;

	public SpriteGroup PLAYER_GROUP;
	public SpriteGroup ENEMY_GROUP;
	public SpriteGroup ITEM_GROUP;

	public SpriteGroup HUD_GROUP;
	public CharacterProfile charProfile;
	protected PlayingProfile playProfile;

	public TiledBackground background;
	protected ImageBackground panelBackground;

	// VARIABLE //

	private Sprite pointer;
	
	public int turn;
	
	public Antz main;
	
	public boolean shown;
	public boolean changeTurn;
	//public boolean is
	//private GameChar dummy;
	
	private GameChar selectedChar;
	public AnimatedSprite attackEffect;
	public AnimatedSprite saveEffect;
	public AnimatedSprite turnEffect;
	
	public int music;
	
	public BufferedImage[] attackEffectImage;
	public BufferedImage[] yourTurnEffectImage;
	public BufferedImage[] enemyTurnEffectImage;

	protected AiManager aiManager;

	/** Tabel penghalang; (hanya) digunakan oleh algoritma path finding. */
	private boolean[][] t_obstacle;
	/** Tabel visited; (hanya) digunakan oleh algoritma path finding. */
	private boolean[][] t_visited;

	private boolean possibleMoveVisible;

	protected boolean isEnemyTurn = false;

	// CONSTRUCTOR //

	public Stage(Antz parent) {
		super(parent);
		main = parent;
	}

	// METHOD //

	@Override
	public void initResources() {
		// Inisialisasi tabel untuk algoritma path finding
		t_obstacle = new boolean[background.getWidth()][background.getHeight()];
		t_visited = new boolean[background.getWidth()][background.getHeight()];

		
		// Main Scene Objects

		background.setClip(0, 0, parent.getWidth() - PANEL_WIDTH, parent.getHeight());

		PLAYER_GROUP = ((Antz) parent).PLAYER_GROUP;

		ENEMY_GROUP = new SpriteGroup("Enemy Group");
		
		music = bsMusic.play("assets/Rainforest-SoundBible.com-1791576435.mp3");
		
		//dummy = new GameChar((Antz) parent, 1);
		//dummy.setActive(false);
		
		//ENEMY_GROUP.add(dummy);

		ITEM_GROUP = new SpriteGroup("Item Group");

		playField = new PlayField(background);
		playField.addGroup(PLAYER_GROUP);
		playField.addGroup(ENEMY_GROUP);
		playField.addGroup(ITEM_GROUP);
		playField.setComparator(new Comparator<Sprite>() {
			public int compare(Sprite o1, Sprite o2) {
				int layerDiff = o1.getLayer() - o2.getLayer();
				if (layerDiff != 0)
					return layerDiff;
				return (int) (o1.getY() + o1.getHeight() - o2.getY() - o2.getHeight());
			}
		});

		loadCharacters();

		
		
		totalAttackUp = 0;
		totalDefenseUp = 0;

		// HUD Objects

		BufferedImage panelImage = getImage("assets/panel3.png");
		panelBackground = new ImageBackground(panelImage);
		panelBackground.setClip(parent.getWidth() - panelImage.getWidth(), 0,
				panelImage.getWidth(), parent.getHeight());

		charProfile = new CharacterProfile(this, 40, 10);
		playProfile = new PlayingProfile(this, 40, 250);
		
		mission = new Sprite(getImage("assets/mission2.png"));
		//mission.setImmutable(false);
		mission.setLocation(100, 0);
		mission.setActive(true);
		
		closeButton = new Sprite(getImage("assets/close.png"));
		//closeButton.setImmutable(false);
		closeButton.setLocation(mission.getX()+mission.getWidth()-40, mission.getY()+60);
		closeButton.setActive(false);

		HUD_GROUP = new SpriteGroup("HUD Group");
		
		hud = new PlayField(panelBackground);
		hud.addGroup(HUD_GROUP);
		hud.addGroup(charProfile);
		hud.addGroup(playProfile);


		// Other Objects

		pointer = new Sprite(getImage("assets/pointer.png"));
		
		attackEffectImage = new BufferedImage[7];
		yourTurnEffectImage = new BufferedImage[10];
		enemyTurnEffectImage = new BufferedImage[10];
		
		for(int i = 0 ; i < 7; i++){
			attackEffectImage[i] = getImage("assets/flash_c_000"+ i +".png");
		}
		
		yourTurnEffectImage[0] = getImage("assets/flash_c_0000.png");
		enemyTurnEffectImage[0] = getImage("assets/flash_c_0000.png");
		
		for(int i = 1 ; i < 10; i++){
			yourTurnEffectImage[i] = getImage("assets/your_turn_"+ i +".png");
			enemyTurnEffectImage[i] = getImage("assets/enemies_turn_"+ i +".png");
		}

		attackEffect = new AnimatedSprite(attackEffectImage);
		attackEffect.setLoopAnim(false);

		saveEffect = new AnimatedSprite(getImages("assets/itemeffects.png",11,1));
		saveEffect.setLoopAnim(false);
		
		turnEffect = new AnimatedSprite(yourTurnEffectImage);
		turnEffect.setLoopAnim(false);
		
		turnEffect.getAnimationTimer().setDelay(200);
		turnEffect.setLocation(0, 100);

		// AI
		aiManager = new AiManager(this, ENEMY_GROUP);

		// Mouse pointer
		bsInput.setMouseVisible(false);
	}

	/**
	 * Load karakter dari map.
	 */
	private void loadCharacters() {
		int count = background.map.getObjectCount(TiledBackground.OBJGROUP_CHAR);
		for (int i = 0; i < count; ++i) {
			String name = background.map.getObjectName(TiledBackground.OBJGROUP_CHAR, i);

			// Berdasarkan object name:
			// + player | - enemy | * ant to pick
			if(name.charAt(0) != '*') {
				boolean isEnemy = name.charAt(0) == '-';
				int type = Integer.parseInt(name.substring(1));
				GameChar sprite = new GameChar((Antz) parent, type, isEnemy);
				sprite.setLocation(background.map.getObjectX(TiledBackground.OBJGROUP_CHAR, i),
						background.map.getObjectY(TiledBackground.OBJGROUP_CHAR, i));
				if (isEnemy) {
					ENEMY_GROUP.add(sprite);
				} else {
					PLAYER_GROUP.add(sprite);
				}
			}
			else {
				int type = Integer.parseInt(name.substring(1));
				ItemSprite sprite = new ItemSprite((Antz) parent, type);
				sprite.setLocation(background.map.getObjectX(TiledBackground.OBJGROUP_CHAR, i),
						background.map.getObjectY(TiledBackground.OBJGROUP_CHAR, i));
				ITEM_GROUP.add(sprite);

			}
		}

	}

	@Override
	public void render(Graphics2D g) {
		g.setColor(new Color(0x298f24));
		g.fillRect(0, 0, background.getWidth(), background.getHeight());
		playField.render(g);
		hud.render(g);
		
		if(mission.isActive()){
			mission.render(g);
			closeButton.render(g);
		}

		pointer.render(g);
		attackEffect.render(g);
		turnEffect.render(g);
		saveEffect.render(g);
	}

	@Override
	public void update(long elapsedTime) {
		playField.update(elapsedTime);
		hud.update(elapsedTime);
		updatePointer(elapsedTime);

		attackEffect.update(elapsedTime);
		turnEffect.update(elapsedTime);
		saveEffect.update(elapsedTime);
		
		attackIndicator(elapsedTime);
		
		mission.update(elapsedTime);
		closeButton.update(elapsedTime);
		
		//System.out.println(mission.isActive());

		//attack(elapsedTime);
		checkDeath(elapsedTime);

		anyNearbyAnt(elapsedTime);

		// Scrolling
		updateScrolling(elapsedTime);
		
		if(shown && turn == 2){
			hideItem();
		}

		// Click action
		//if(getSelectedCharacter()!=null)
		//System.out.println((getSelectedCharacter().getX()+background.getX()) + " " + (getSelectedCharacter().getY()+background.getY()));

		// Update character control (move & possible moves)
		if (getSelectedCharacter() != null){
			updateCharacterControl(elapsedTime);
		}

		// Update character profile viewer
		if (getSelectedCharacter() != null) {
			charProfile.update(selectedChar, selectedChar.getImage());
		}
	}

	private void updateCharacterControl(long elapsedTime) {
		// Mouse control
		if (click() && background.getClip().contains(getMouseX(), getMouseY()) && !mission.isActive()) {
			if (possibleMoveVisible) {
				// player mengklik ketika possible moves tampil, pindah jika mungkin
				setPossibleMoveVisible(false);
				boolean canMove = getSelectedCharacter().tryMoveTo(
						getMouseX() + (int) background.getX(),
						getMouseY() + (int) background.getY());
				if (!canMove)
					setSelectedCharacter(null);
			} else if (getSelectedCharacter().getStatus() == GameChar.STATE_STAND
					&& getSelectedCharacter().isMouseOver()) {
				// possible moves tidak tampil, tampilkan
				setPossibleMoveVisible(true);
			}
		}
	}

	/**
	 * Update scrolling sesuai posisi mouse di layar.
	 * @param elapsedTime elapsedTime di {@link #update(long)}
	 */
	private void updateScrolling(long elapsedTime) {
		if (getMouseX() < SCROLL_AREA_WIDTH) {
			background.setLocation(background.getX() - elapsedTime * SCROLL_SPEED,
					background.getY());
		} else if (getMouseX() > getWidth() - SCROLL_AREA_WIDTH) {
			background.setLocation(background.getX() + elapsedTime * SCROLL_SPEED,
					background.getY());
		}
		if (getMouseY() < SCROLL_AREA_WIDTH) {
			background.setLocation(background.getX(), background.getY() -
					elapsedTime * SCROLL_SPEED);
		} else if (getMouseY() > getHeight() - SCROLL_AREA_WIDTH) {
			background.setLocation(background.getX(), background.getY() +
					elapsedTime * SCROLL_SPEED);
		}
	}

	/**
	 * Update pointer, hanya dipanggil di method {@link #update(long)}.
	 *
	 * Pointer merupakan penunjuk yang mengikuti mouse.
	 * @param elapsedTime
	 */
	private void updatePointer(long elapsedTime) {
		// Mouse pointer
		if (bsInput.isMouseExists()) {
			pointer.setLocation(getMouseX(), getMouseY());
		}else {
			pointer.setLocation(-pointer.getWidth(), -pointer.getHeight());
		}
	}

	/**
	 * Return mouse position in tile unit.
	 * @return coordinate-x
	 */
	public int getMousseTileX() {
		return getMouseX() / background.getTileWidth();
	}

	/**
	 * Return mouse position in tile unit.
	 * @return coordinat-y
	 */
	public int getMouseTileY() {
		return getMouseY() / background.getTileHeight();
	}

	@Override
	public void finish() {
		super.finish();

		// Memanggil Stage.entered untuk stage berikutnya
		GameObject next = parent.getGame(parent.nextGameID);
		if (next instanceof Stage) {
			((Stage) next).entered(this);
		}
	}

	/**
	 * Override method ini untuk melakukan sesuatu setiap kali stage dimasuki.
	 *
	 * Secara default, method ini tidak melakukan apa-apa.
	 * @param before stage sebelumnya
	 */
	public void entered(GameObject before) {
	}

	/**
	 * Ganti fokus karakter.
	 * @param c karakter yang di-select
	 */
	public void setSelectedCharacter(GameChar c) {
		if (selectedChar != null) {
			selectedChar.setControlState(GameChar.CONTROL_DISABLED);
		}
		if (c != null) {
			c.setControlState(GameChar.CONTROL_ENABLED);
			charProfile.setVisible(true);
		} else {
			charProfile.setVisible(false);
		}
		selectedChar = c;

		//System.out.println(selectedChar);
	}


	public PathFindingNode trace(int startX, int startY, int targetX,
			int targetY) {
		return trace(startX, startY, targetX, targetY, Integer.MAX_VALUE);
	}

	/**
	 * Lakukan path finding terhadap tile (tempat) yang dituju dari posisi
	 * karakter dengan memperhatikan penghalang.
	 * @param startX koordinat-x karakter saat ini
	 * @param startY koordinat-y karakter saat ini
	 * @param targetX koordinat-x yang dituju
	 * @param targetY koordinat-y yang dituju
	 * @return path finding node yang pertama
	 */
	public PathFindingNode trace(int startX, int startY, int targetX,
			int targetY, int distance) {
		// Reset obstacle
		for (boolean[] i : t_obstacle)
			Arrays.fill(i, false);

		// Baca lokasi karakter pemain untuk menge-set penghalang
		for (Sprite i : PLAYER_GROUP.getSprites()) {
			if (i == null || !i.isActive())
				continue;
			t_obstacle[(int) i.getX() / background.getTileWidth()]
			           [(int) i.getY() / background.getTileHeight()] = true;
		}

		// Baca lokasi karakter lawan untuk menge-set penghalang
		for (Sprite i : ENEMY_GROUP.getSprites()) {
			if (i == null || !i.isActive())
				continue;
			t_obstacle[(int) i.getX() / background.getTileWidth()]
			           [(int) i.getY() / background.getTileHeight()] = true;
		}

		// Trace secara BFS
		return background.trace(startX, startY, targetX, targetY, t_obstacle,
				t_visited, distance);
	}

	protected boolean isTurnFinished(SpriteGroup group){
		boolean finished = false;
		GameChar temp;

		for (Sprite i : group.getSprites()) {
			if (i == null || !i.isActive())
				continue;

			temp = (GameChar)i;

			if(temp.status.energy == 0){
				finished = true;
			}
			else {
				return false;
			}

		}

		return finished;
	}

	protected void replenishEnergy(SpriteGroup group){

		GameChar temp;

		for (Sprite i : group.getSprites()) {
			if (i == null || !i.isActive())
				continue;

			temp = (GameChar)i;

			temp.status.energy = temp.status.maxEnergy;
		}
	}

	/*private void attack(long elapsedTime){
		GameChar target;
		GameChar attacker;

		attacker = getSelectedCharacter();

		target = anyEnemyNearby(attacker);
		
		if(target!=null && click()){
			if( getMouseX() == target.getX() && getMouseY() == target.getY()){
				
			}
		}

		if(target!=null && keyPressed(KeyEvent.VK_SPACE) && attacker.status.energy - 30 >= 0){
			target.status.health -= attacker.status.damage;
			attackEffect.setLocation(target.getX() - background.getX(), target.getY()- background.getY());
			attackEffect.setAnimate(true);
			attacker.status.energy -= attacker.status.energyUsed*3;
		}
	}*/

	private GameChar anyEnemyNearby(GameChar attacker){
		int x = 0;
		int y = 0;

		int xTemp = 0;
		int yTemp = 0;

		GameChar enemy = null;

		if(attacker!=null){
			x = (int)attacker.getX() / background.getTileWidth();
			y = (int)attacker.getY() / background.getTileWidth();
		}

		//System.out.println(x + " " + y);

		for (Sprite i : ENEMY_GROUP.getSprites()) {
			if (i == null || !i.isActive())
				continue;

			xTemp = (int)i.getX() / background.getTileWidth();
			yTemp = (int)i.getY() / background.getTileWidth();

			if((xTemp-1 == x && yTemp==y) || (xTemp+1 == x && yTemp==y) || (xTemp == x && yTemp+1==y) ||
					(xTemp == x && yTemp-1==y)){

				enemy = (GameChar)i;

				return enemy;
			}
		}

		return enemy;
	}

	private void checkDeath(long elapsedTime){
		GameChar temp;

		for (Sprite i : ENEMY_GROUP.getSprites()) {
			if (i == null || !i.isActive())
				continue;

			temp = (GameChar)i;

			if(temp.status.health <= 0)
				i.setActive(false);
		}

		for (Sprite i : PLAYER_GROUP.getSprites()) {
			if (i == null || !i.isActive())
				continue;

			temp = (GameChar)i;

			if(temp.status.health <= 0){
				if(temp.status.type == Status.Z){
					bsMusic.stop(music);
					parent.nextGameID = Antz.GAME_OVER;
					finish();
				}
				main.dead.add(temp);
				i.setActive(false);
			}
		}
	}

	public GameChar getSelectedCharacter(){
		return selectedChar;
	}

	private void anyNearbyAnt(long elapsedTime){
		int x = 0;
		int y = 0;

		int xTemp = 0;
		int yTemp = 0;

		GameChar current = getSelectedCharacter();
		ItemSprite temp;

		if(current!=null){
			x = (int)current.getX() / background.getTileWidth();
			y = (int)current.getY() / background.getTileWidth();
		}

		for (Sprite i : ITEM_GROUP.getSprites()) {
			if (i == null){
				continue;
			}

			xTemp = (int)i.getX() / background.getTileWidth();
			yTemp = (int)i.getY() / background.getTileWidth();

			temp = (ItemSprite)i;

			if(x <= xTemp+2 && x >= xTemp-2 && y <= yTemp+2 && y >= yTemp-2 && !temp.isSaved ){
				i.setActive(true);
			}

			if(x == xTemp && y == yTemp && i.isActive()){
				
				bsSound.play("assets/nambahdarah.mp3");
				//saveEffect.setActive(true);
				//saveEffect.setLocation(xTemp, yTemp);
				//saveEffect.setAnimate(true);
				
				animateAttack(temp, saveEffect);
				
				if(temp.type != ItemSprite.GOAL){
					i.setActive(false);
				    temp.isSaved = true;
				}

				if(temp.type == ItemSprite.ANTZ){
					main.antSaved += 10;
					playProfile.setAntSaved(main.antSaved);
				}
				else if(temp.type == ItemSprite.POTION){
					main.healthPotion++;
				}
				else if(temp.type == ItemSprite.ENERGY_POTION){
					main.energyPotion++;
				}
				else if(temp.type == ItemSprite.GOAL){
					parent.nextGameID = Antz.POST_LEVEL;
					finish();
				}
				else if(temp.type == ItemSprite.MAP){
					turn = 0;
					shown = true;
					showHiddenItem();
				}
			}
		}
	}

	private void showHiddenItem() {
		// TODO Auto-generated method stub
		for (Sprite i : ITEM_GROUP.getSprites()) {
			if (i == null){
				continue;
			}
			
			if(!((ItemSprite)i).isSaved && ((ItemSprite)i).type != ItemSprite.GOAL )
				i.setActive(true);
		}
	}
	
	private void hideItem(){
		
		for (Sprite i : ITEM_GROUP.getSprites()) {
			if (i == null || ((ItemSprite)i).type == ItemSprite.GOAL){
				continue;
			}
			
			shown = false;
			turn = 0;
			i.setActive(false);
		}
	}

	/**
	 * Event yang ditrigger oleh {@link GameChar} ketika ia di-klik, walaupun
	 * ia sedang tak {@link GameChar#CONTROL_ENABLED CONTROL_ENABLED}.
	 * @param gameChar event sender
	 */
	public void onCharClicked(GameChar gameChar) {
		GameChar temp;
		
		if (getSelectedCharacter() == null || getSelectedCharacter().getStatus()
				== GameChar.STATE_STAND) {
			// selected character saat ini tidak sedang berjalan

			if (isEnemyTurn && Util.groupContains(ENEMY_GROUP, gameChar))
				setSelectedCharacter(gameChar);
			else if (!isEnemyTurn && Util.groupContains(PLAYER_GROUP, gameChar))
				setSelectedCharacter(gameChar);
		}
		
		temp = getSelectedCharacter();
		
		if(gameChar.isEnemy && temp != null && anyEnemyNearby(temp)!=null && temp.status.energy - 3*temp.status.energyUsed >= 0){
			bsSound.play("assets/punch_or_whack_-Vladimir-403040765.mp3");
			animateAttack(gameChar, attackEffect);
			temp.setAttackDirection(gameChar);
			gameChar.status.health -= getSelectedCharacter().status.damage;
			temp.status.energy -= 3*temp.status.energyUsed;
			setSelectedCharacter(temp);
		}
	}
	
	public void attackIndicator(long elapsedTime){
		GameChar temp = anyEnemyNearby(getSelectedCharacter());
		
		if(temp != null && temp.isMouseOver()&& getSelectedCharacter().status.energy - 3*getSelectedCharacter().status.energyUsed >= 0){
			pointer.setImage(getImage("assets/pointer-attack.png"));
			//System.out.println("bisa nyerang loh");
		}
		else if(temp != null && temp.isMouseOver()){
			pointer.setImage(getImage("assets/pointer-energy.png"));
		}
		else {
			pointer.setImage(getImage("assets/pointer.png"));
		}
	}

	/**
	 * Tampilkan atau sembunyikan penanda jalan yang dapat ditempuh oleh
	 * {@link #selectedChar}.
	 * @param visible tampilkan atau tidak
	 */
	private void setPossibleMoveVisible(boolean visible) {
		possibleMoveVisible = visible;
		if (visible) {
			// Tandai tempat-tempat yang bisa ditempuh pada map

			// Cari possible moves
			trace((int) selectedChar.getX(), (int) selectedChar.getY(),
					-background.getTileWidth(), -background.getTileHeight(),
					selectedChar.status.energy / selectedChar.status.energyUsed);
			// tile id dari selection mark untuk penanda
			int tileid = background.map.getTileSet(0).firstGID;
			int width = background.map.getWidth();
			int height = background.map.getHeight();
			for (int y = 0; y < height; ++y)
				for (int x = 0; x < width; ++x) {
					background.map.setTileId(x, y, TiledBackground.LAYER_SELECTION,
							t_visited[x][y] ? tileid : 0);
				}
		} else {
			// Bersihkan penanda dari map

			int width = background.map.getWidth();
			int height = background.map.getHeight();
			for (int y = 0; y < height; ++y)
				for (int x = 0; x < width; ++x) {
					background.map.setTileId(x, y, TiledBackground.LAYER_SELECTION, 0);
				}
		}
	}

	/**
	 * Maksudnya sih mengganti turn.
	 */
	public void switchTurn() {
		isEnemyTurn = !isEnemyTurn;
		if (isEnemyTurn) {
			replenishEnergy(ENEMY_GROUP);
		} else {
			replenishEnergy(PLAYER_GROUP);
		}
		
		changeTurn = true;
			
		setSelectedCharacter(null);
		setPossibleMoveVisible(false);
	}

	public boolean isPlayerTurn() {
		return !isEnemyTurn;
	}
	
	public void animateAttack(Sprite target , AnimatedSprite effect){
		effect.setLocation(target.getX()- background.getX(), target.getY()- background.getY());
		effect.setAnimate(true);
		
	}
}
